<?php
namespace Fyfyu\Swagger;

use Hyperf\Di\Annotation\AnnotationCollector;
use Hyperf\HttpServer\Router\DispatcherFactory;
use Hyperf\HttpServer\Router\Handler;
use Psr\Container\ContainerInterface;

use Fyfyu\Swagger\Annotation\Api;
use Fyfyu\Swagger\Annotation\ApiOperation;
use Fyfyu\Swagger\Annotation\ApiParams;
use Fyfyu\Swagger\Annotation\ApiParam;
use Fyfyu\Swagger\Annotation\ApiResponses;
use Hyperf\Utils\Str;

class Swagger
{
    protected $container;

    public function __construct(ContainerInterface $container)
    {
        $this->container = $container;
    }

    public function buildDoc($server='http', $path='')
    {
        $docs = [];
        $table_docs = [];
        $factory = $this->container->get(DispatcherFactory::class);
        $router = $factory->getRouter($server);
        $routes = $this->getRouteList($router, $path);
        // $api            =   AnnotationCollector::getClassByAnnotation(Api::class);
        $apiOperation   =   AnnotationCollector::getMethodByAnnotation(ApiOperation::class);
        $apiParams      =   AnnotationCollector::getMethodByAnnotation(ApiParams::class);
        // $apiParam       =   AnnotationCollector::getMethodByAnnotation(ApiParam::class);
        $apiResponse    =   AnnotationCollector::getMethodByAnnotation(ApiResponses::class);

        // print_r(compact('api', 'apiOperation', 'apiParams', 'apiParam', 'apiResponse'));

        foreach ($apiOperation as $item) {
            $key = $this->getKey($item);
            if (! isset($routes[$key])) {
                continue;
            }
            $docs[$key] = array_merge(
                $item['annotation']->toArray(),
                $routes[$key]
            );
            $docs[$key]['params'] = [];
        }
        foreach ($apiParams as $item) {
            $key = $this->getKey($item);
            if (! isset($routes[$key])) {
                continue;
            }
            $docs[$key]['params'] = $this->getParams($item['annotation']);
        }

        foreach ($apiResponse as $item) {
            $key = $this->getKey($item);
            if (! isset($routes[$key])) {
                continue;
            }

            $docs[$key]['params'] = array_merge(
                $docs[$this->getKey($item)]['params'] ?? [],
                $this->getParams($item['annotation'])
            );
        }

        return array_values($docs);
    }
    /**
     * class + method 组成key
     * @param  array $item AnnotationCollector
     * @return string       class::method
     */
    protected function getKey($item)
    {
        return $item['class'].'::'.$item['method'];
    }

    /**
     * 处理参数
     * @var object $annotation AnnotationCollector::getMethodByAnnotation
     * @return array
     */
    protected static function getParams($annotation)
    {
        $params = [];
        if ($annotation->type == 'group') {
            $params = collect($annotation->params)->reduce(function ($prev, $item) {
                return array_merge(
                    $prev,
                    Swagger::getParams($item)
                );
            }, []);
        } else {
            $params[] = [
                'name'  => $annotation -> name,
                'type'  => $annotation -> type,
                'datas' => collect($annotation->params)->map(function ($item) {
                    return $item->toArray();
                })->toArray()
            ];
        }
        return $params;
    }

    /**
     * 获取路由信息
     * @param  object $router DispatcherFactory::class
     * @return array
     */
    protected function getRouteList($router, $path)
    {
        $list = [];
        [$staticRouters, $variableRouters] = $router->getData();
        foreach ($staticRouters as $method => $items) {
            foreach ($items as $handler) {
                $this->analyzeHandler($list, $method, $handler, $path);
            }
        }
        foreach ($variableRouters as $method => $items) {
            foreach ($items as $item) {
                if (is_array($item['routeMap'] ?? false)) {
                    foreach ($item['routeMap'] as $routeMap) {
                        $this->analyzeHandler($list, $method, $routeMap[0], $path);
                    }
                }
            }
        }
        return $list;
    }

    protected function analyzeHandler(array &$data, string $method, Handler $handler, ?string $path)
    {
        if (! is_null($path) && ! Str::contains($handler->route, $path)) {
            return;
        }
        if (is_array($handler->callback)) {
            $action =  implode('::', $handler->callback);
        } elseif (is_string($handler->callback)) {
            $action = $handler->callback;
        } elseif (is_callable($handler->callback)) {
            $action = 'Closure';
        } else {
            $action = (string) $handler->callback;
        }
        $data[$action] = [
            'url' => $handler->route,
            'method' => $method
        ];
    }
}
